package algorithms;

import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import register.Register;
import gates.Factory;
import gui.UserInterface;

/**
 * Class defining Grovers Circuit, the GUI to display the circuit results and all gates contained within the circuit.
 * Main role is handling the UI.
 * @author Matt Cole, Alex Bush 
 */
public class GroverCircuit implements ActionListener {
	/**Minimum number of allowed qubits*/
	private final int minNumQubits = 1;
	/**Maximum number of allowed qubits, as limited by classical architecture*/
	private final int maxNumQubits = 16;
	/**Store the UI object to give the gui*/
	private UserInterface UI;
	/**number of qubits in register*/
	protected int numQubits;
	/**Quantum register*/
	protected Register reg;
	/**number of iterations*/
	protected int iteration;	
	/**element being searched for*/
	protected int searchElem;
	/**boolean to determine whether or not the vector plot needs updating*/
	protected boolean updateVectorPlot;
	/**optimal number of grover iterations. This is based on equations found in Quantum 
	 * Computing and Quantum Information */
	private int optimal;
	/**number of states in the register*/
	private double numStates;
	/**message giving the optimal number of iterations*/
	private String optimalString;
	
	// Stores the number of the qubit to be prepared. If this is less than the number of qubits in the
	// register then the nextStep() method will prepare the resister accordingly rather than applying the next
	// iteration of the algorithm.
	protected int qubitPrepare;					
	
	public GroverCircuit() {
		iteration = 0;
		numQubits = 0;
		qubitPrepare = 0;	
		updateVectorPlot = false;
	}
	
	/**
	 * Initiate the program.
	 */
	public void runAlgorithm() {
		// Create the UI. Pass "this" so that Circuit can be added as an action
		// listener to the buttons on the interface
		UI = new UserInterface(this);
		UI.showIntro();
	}
	
	/**
	 * Controls what the program does when a button is clicked.
	 */
	public void actionPerformed(ActionEvent event) {
		
		// Next button on intro window. The following code simply retrieves the user
		// input and checks its validity
		if(event.getActionCommand().equals("next")) {
			numQubits = 0;
			boolean validRegSize = false; 
			boolean validSearchElem = false;
			boolean correctDataType = false;
			
			// Check that the inputs are integers
			try {
				numQubits = Integer.parseInt(UI.getNumQubits());
				searchElem = Integer.parseInt(UI.getSearchElem());
				correctDataType = true;
			}
			catch(NumberFormatException e) {
				UI.inputError("Enter integers only.");
				UI.showIntro();
			}
			
			// Check that the numQubits is acceptable
			if(correctDataType) {
				if(numQubits >= minNumQubits && numQubits <= maxNumQubits) {
					validRegSize = true;
				}
				else {
					validRegSize = false;
					UI.inputError("Enter a register size between "
							+ minNumQubits + " and " + maxNumQubits + ".");
					UI.showIntro();
				}
				
				// Check that the searchElem size is acceptable		
				if(validRegSize) {
					int maxSearchElem = (int)Math.pow(2,numQubits)-1;
					if(searchElem >= 0 && searchElem <= maxSearchElem) {
						validSearchElem = true;
					}
					else {
						validSearchElem = false;
						UI.inputError("Enter a search element between 1 and "
								+ maxSearchElem + ".");
						UI.showIntro();
					}
					
					// If all tests are passed, proceed to the next step by displaying the main window
					if(validRegSize && validSearchElem) {
						reg = new Register(numQubits);
						numStates = Math.pow(2,numQubits);
						double optimalDoub = Math.acos(Math.sqrt(1.0/numStates))/  (Math.asin(2*Math.sqrt(numStates-1)/numStates));
						if((optimalDoub*2)%2==1){
							optimal=(int)Math.floor(optimalDoub);
						}
						else{
							optimal = Math.round((float)optimalDoub);
						}
						optimalString = "The optimal number of Grover iterations is " + optimal + ".\n";
						UI.showMain(reg, searchElem);
						UI.enableMeasureButton();
						UI.printMessage(getName() + " with a " + numQubits + " qubit register." + "\n"+
								" The search element is " + searchElem + ".\n" +
								optimalString);
					}
					
				}
			}
		}
		
		// Exit button closes the program
		else if(event.getActionCommand().equals("exit")) {
			System.exit(0);
		}
		
		// Next step button on main window calls the next step method
		// and updates the screen
		else if(event.getActionCommand().equals("next_step")) {
			// Performs the next step of the algorithm, storing the action taken as a string
			String action = nextStep();
			// Pass the newly altered register along with the action taken to the UI
			UI.update(reg, action, updateVectorPlot);
		}
		
		// Measure button on the main window
		else if(event.getActionCommand().equals("measure")) {
			String result = measureRegister();
			UI.update(reg, result, false);
			UI.disableStepButtons();
		}
		
		// Reset button on the main window
		else if(event.getActionCommand().equals("reset")) {
			iteration = 0;
			qubitPrepare = 0;
			reg.setToGroundState();
			UI.showMain(reg, searchElem);
			UI.printMessage("Reset " + getName() + " with a " + numQubits + " qubit register." +
					" The search element is " + searchElem + ".\n" + optimalString);
			UI.enableMeasureButton();
		}
		
		// New simulatoin button on the main window
		else if(event.getActionCommand().equals("new")) {
			iteration = 0;
			qubitPrepare = 0;
			UI.showIntro();
		}
		
	}
	
	/**
	 * Provide the next step of the iteration.
	 * Initially applies <code>numQubits</code> number of Hadamard gates, one for each qubit,
	 * to prepare the register. This will not increment the counter.
	 * Then performs as many iteration steps as the user wants.
	 * @return String to print to the UI.
	 */
	public String nextStep() {
		if (qubitPrepare < numQubits){
			
			// The vector panel does not need to be updated until the last preparation
			updateVectorPlot = false;
			
			// If this is the last preparation then the vector panel will need to be updated
			if (qubitPrepare == numQubits-1){
				updateVectorPlot = true;
			}
			
			// Prepare the register for the current prepare qubit
			String action = prepareRegister();
			qubitPrepare++;
			return action;
		}
		else {
			
			// The vector plot will be updated next time the UI is updated.
			updateVectorPlot = true;
			return performIteration();
			
		}
	}
	
	/**
	 * Measures the register returning information about the measurement. 
	 * Will only make measurement if the register has been prepared.
	 * @return Result, number of iterations taken and expected result. To be printed to the UI.
	 */
	public String measureRegister() {
	
		// Reset the vector diagram as no long valid
		UI.removeRegStateVector();
		
		// Apply the measurement to the register
		Factory.createMeasurement().apply(reg);
		int result = reg.postMeasure();
		String output = getName() + " returns a result of " + result + " after " + iteration +
		" iteration(s). The expected result is " + searchElem + ".";
		if(qubitPrepare<numQubits){
			output += "   WARNING: Register was not fully prepared";
		}
		return output;
	}
	

	/**
	 * Applies the algorithm to the register.
	 * @return String to print to the UI.
	 */
	public String performIteration() {
		Factory.createGroverIteration(searchElem, numQubits).apply(reg);
		iteration++;
		return "Performed Grover iteration "+iteration+" on the register.";
	}

	/**
	 * Prepares the specified qubit of the register.
	 * @return String to print to the UI.
	 */
	public String prepareRegister() {
		
		// Perform the preparation
		Factory.createHadamard(qubitPrepare).apply(reg);
		String action = "Prepared qubit " + qubitPrepare + " by applying a Hadamard gate with target bit "
		+ qubitPrepare + " to the register.";
		
		// If the register is now fully prepared, measurements may now be taken
		if (qubitPrepare == numQubits-1){
			action += " The register is now fully prepared.";
		}
		
		// Return action taken as a string
		return action;
		
	}

	/**
	 * Return the name of the current algorithm.
	 * @return Name of algorithm.
	 */
	public String getName() {
		return "Grovers Algorithm";
	}
	
	/**
	 * Return the minimum allowed number of qubits
	 * @return minNumQubits
	 */
	public int getMinNumQubits() {
		return minNumQubits;
	}

	/**
	 * Return the maximum allowed number of qubits
	 * @return maxNumQubits
	 */
	public int getMaxNumQubits() {
		return maxNumQubits;
	}

}
